# Copyright 2004 Daniel Henninger <jadestorm@nc.rr.com>
# Licensed for distribution under the GPL version 2, check COPYING for details

from twisted.web.microdom import Element
from twisted.internet import protocol, reactor, defer
#from twisted.protocols import oscar
from tlib import oscar
from twisted.python import log
import config
import utils
import debug
import sys, warnings, pprint



#############################################################################
# BOSConnection
#############################################################################
class B(oscar.BOSConnection):
	def __init__(self,username,cookie,aimcon):
		self.aimcon = aimcon
		self.aimcon.bos = self
		self.session = aimcon.session  # convenience
		self.capabilities = [oscar.CAP_CHAT]
		oscar.BOSConnection.__init__(self,username,cookie)

	def initDone(self):
		self.requestSelfInfo().addCallback(self.gotSelfInfo)
		self.requestSSI().addCallback(self.gotBuddyList)
		debug.log("B: initDone %s for %s" % (self.username,self.session.jabberID))

	def updateBuddy(self, user):
		from glue import aim2jid

		debug.log("B: updateBuddy %s" % (user))
		buddyjid = aim2jid(user.name)
		ptype = None
		show = None
		status = None
		if (user.flags.count("away")):
			self.getAway(user.name).addCallback(self.sendAwayPresence, user)
		else:
			self.session.sendPresence(to=self.session.jabberID, fro=buddyjid, show=show, status=status, ptype=ptype)
			self.aimcon.contacts.updateSSIContact(user.name, presence=ptype, show=show, status=status)

	def offlineBuddy(self, user):
		from glue import aim2jid

		debug.log("B: offlineBuddy %s" % (user.name))
		buddyjid = aim2jid(user.name)
		show = None
		status = None
		ptype = "unavailable"
		self.session.sendPresence(to=self.session.jabberID, fro=buddyjid, show=show, status=status, ptype=ptype)

	def receiveMessage(self, user, multiparts, flags):
		from glue import aim2jid

		debug.log("B: receiveMessage %s %s %s %s %s" % (self.session.jabberID, self.name, user.name, multiparts, flags))
		sourcejid = aim2jid(user.name)
		text = oscar.dehtml(multiparts[0][0])
		text = text.decode("utf-8", "replace")
		text = text.strip()
		self.session.sendMessage(to=self.session.jabberID, fro=sourcejid, body=text, mtype="chat")

	def receiveWarning(self, newLevel, user):
		debug.log("B: receiveWarning [%s] from %s" % (newLevel,hasattr(user,'name') and user.name or None))

	def receiveChatInvite(self, user, message, exchange, fullName, instance, shortName, inviteTime):
		debug.log("B: receiveChatInvite from % for room % with message: %s" % (user.name,shortName,message))

	def chatReceiveMessage(self, chat, user, message):
		debug.log("B: chatReceiveMessage to %s from %s:%s" % (chat.name,user.name,message))

	def chatMemberJoined(self, chat, member):
		debug.log("B: chatMemberJoined %s joined %s" % (member.name,chat.name))

	def chatMemberLeft(self, chat, member):
		debug.log("B: chatMemberLeft %s left %s (members: %s)" % (member.name,chat.name,map(lambda x:x.name,chat.members)))

	def receiveSendFileRequest(self, user, file, description, cookie):
		debug.log("B: receiveSendFileRequest")


	# Callbacks
	def sendAwayPresence(self, msg, user):
		from glue import aim2jid
        
		buddyjid = aim2jid(user.name)
		ptype = None
		show = "away"
		status = msg
		self.session.sendPresence(to=self.session.jabberID, fro=buddyjid, show=show, status=status, ptype=ptype)
		self.aimcon.contacts.updateSSIContact(user.name, presence=ptype, show=show, status=status)

	def gotSelfInfo(self, user):
		debug.log("B: gotSelfInfo: %s" % (user.__dict__))
		self.name = user.name

	def gotBuddyList(self, l):
		debug.log("B: gotBuddyList: %s" % (str(l)))
		self.ssigroups = list()
		if (l is not None):
			for g in l[0]:
				debug.log("B: gotBuddyList found group %s" % (g.name))
				self.ssigroups.append(g)
				for u in g.users:
					debug.log("B: got user %s from group %s" % (u.name, g.name))
					self.aimcon.contacts.updateSSIContact(u.name, skipsave=True)
		self.aimcon.contacts.saveXDBBuddies()
		self.activateSSI()
		self.setIdleTime(0)
		self.clientReady()
		self.session.ready = True
		tmpjid=config.jid
		if(self.session.registeredmunge):
			tmpjid=config.jid+"/registered"
		self.session.sendPresence(to=self.session.jabberID, fro=tmpjid, show=self.aimcon.savedShow, status=self.aimcon.savedFriendly)
		if (self.aimcon.savedShow in ["online", None]):
			self.aimcon.setAway(None)
		else:
			self.aimcon.setAway(self.aimcon.savedFriendly)

	def warnedUser(self, oldLevel, newLevel, username):
		debug.log("B: warnedUser");

	def createdRoom(self, (exchange, fullName, instance)):
		debug.log("B: createdRoom: %s, %s, %s" % (exchange, fullName, instance))

	def chatJoined(self, chat):
		debug.log("B: chatJoined room %s (members: %s)" % (chat.name,map(lambda x:x.name,chat.members)))



#############################################################################
# Oscar Authenticator
#############################################################################
class OA(oscar.OscarAuthenticator):
	def __init__(self,username,password,aimcon,deferred=None,icq=0):
		self.aimcon = aimcon
		self.BOSClass = B
		oscar.OscarAuthenticator.__init__(self,username,password,deferred,icq)

	def connectToBOS(self, server, port):
		c = protocol.ClientCreator(reactor, self.BOSClass, self.username, self.cookie, self.aimcon)
		return c.connectTCP(server, int(port))



#############################################################################
# AIMConnection
#############################################################################
class AIMConnection:
	def __init__(self, username, password):
		self.username = username
		self.password = password
		self.reactor = reactor
		self.contacts = AIMContacts(self.session)
		self.deferred = defer.Deferred()
		self.deferred.addErrback(self.errorCallback)
		hostport = ("login.oscar.aol.com", 5109)
		debug.log("AIMConnection: client creation for %s" % (self.session.jabberID))
		self.oa = OA
		self.creator = protocol.ClientCreator(self.reactor, self.oa, self.username, self.password, self, deferred=self.deferred, icq=0)
		debug.log("AIMConnection: connect tcp")
		self.creator.connectTCP(*hostport)

		debug.log("AIMConnection: \"%s\" created" % (self.username))
	
	def setAway(self, awayMessage=None):
		debug.log("AIMConnection: setAway %s" % (awayMessage))
		if (not self.session.ready or not hasattr(self, "bos")):
			return

		self.bos.setAway(awayMessage)

	def sendMessage(self, target, message):
		from glue import jid2aim

		scrnname = jid2aim(target)
		debug.log("AIMConnection: sendMessage %s %s" % (scrnname, message))
		htmlized = oscar.html(message.encode("iso-8859-1", "replace"))
		if (hasattr(self, "bos")):
			self.bos.sendMessage(scrnname, htmlized)
		else:
			debug.log("AIMConnection: not logged in yet")
			return

	def resendBuddies(self, resource):
		from glue import aim2jid
		debug.log("AIMConnection: resendBuddies %s" % (resource))
		if (not hasattr(self, "contacts")):
			return

		for c in self.contacts.ssicontacts.keys( ):
			debug.log("AIMConnection: resending buddy of %s" % (c))
			jid = aim2jid(c)
			show = self.contacts.ssicontacts[c]['show']
			status = self.contacts.ssicontacts[c]['status']
			ptype = self.contacts.ssicontacts[c]['presence']
			self.session.sendPresence(to=self.session.jabberID, fro=jid, show=show, status=status, ptype=ptype)

	def getvCard(self, vcard, user):
		debug.log("AIMConnection: getvCard %s" % (user))
		d = defer.Deferred()
		self.bos.getProfile(user).addCallback(self.gotvCard, user, vcard, d)
		return d

        def gotvCard(self, profile, user, vcard, d):
		debug.log("AIMConnection: gotvCard: %s" % (profile))

		profilestart = profile.find("<HTML>")
		cutprofile = oscar.dehtml(profile[profilestart:])
		nickname = vcard.addElement("NICKNAME")
		nickname.addContent(user)
		jabberid = vcard.addElement("JABBERID")
		jabberid.addContent(user+"@"+config.jid)
		desc = vcard.addElement("DESC")
		desc.addContent(cutprofile)

		d.callback(vcard)

	def removeMe(self):
		from glue import aim2jid
		debug.log("AIMConnection: removeMe")
		if (not hasattr(self, "bos")):
			return

		self.bos.stopKeepAlive()
		self.bos.disconnect()

		if (not hasattr(self, "contacts")):
			return

		for c in self.contacts.ssicontacts.keys( ):
			debug.log("AIMConnection: sending offline for %s" % (c))
			jid = aim2jid(c)
			show = self.contacts.ssicontacts[c]['show']
			status = self.contacts.ssicontacts[c]['status']
			ptype = self.contacts.ssicontacts[c]['presence']
			self.session.sendPresence(to=self.session.jabberID, fro=jid, show=show, status=status, ptype=ptype)

	def jabberSubscriptionReceived(self, to, subtype):
		debug.log("AIMConnection: Session \"%s\" - jabberSubscriptionReceived(\"%s\", \"%s\")" % (self.session.jabberID, to, subtype))

		def updatePresence(ptype): # Convenience
			self.session.sendPresence(to=self.session.jabberID, fro=to, ptype=ptype)

		if(to.find('@') > 0): # For contacts
			from glue import jid2aim

			userHandle = jid2aim(to)

			if(subtype == "subscribe"):
				# User wants to subscribe to contact's presence
				debug.log("AIMConnection: Subscribe request received.")
				def cb(arg=None):
					updatePresence("subscribed")

				if (not hasattr(self, "bos")):
					debug.log("Not properly logged in yet")
					return

				savethisgroup = None
				for g in self.bos.ssigroups:
					if (g.name == "Buddies"):
						debug.log("Located group %s" % (g.name))
						savethisgroup = g

				if (savethisgroup is None):
					debug.log("Need to add a new group")
					return

				newUser = oscar.SSIBuddy(userHandle)
				newUserID = len(savethisgroup.users)+1
				savethisgroup.addUser(newUserID, newUser)

				debug.log("Adding item to SSI")
				self.bos.startModifySSI()
				self.bos.addItemSSI(newUser).addCallback(cb)
				self.bos.modifyItemSSI(savethisgroup)
				self.bos.endModifySSI()

				self.contacts.updateSSIContact(userHandle)

			elif(subtype == "subscribed"):
				# The user has granted this contact subscription
				debug.log("AIMConnection: Subscribed request received.")
				pass

			elif(subtype == "unsubscribe"):
				# User wants to unsubscribe to this contact's presence. (User is removing the contact from their list)
				debug.log("AIMConnection: Unsubscribe request received.")
				def cb(arg=None):
					updatePresence("unsubscribed")

				savethisuser = None
				for g in self.bos.ssigroups:
					for u in g.users:
						debug.log("Comparing %s and %s" % (u.name.lower(), userHandle))
						if (u.name.lower() == userHandle):
							debug.log("Located user %s" % (u.name))
							savethisuser = u

				if (savethisuser is None):
					debug.log("Did not find user")
					return

				self.bos.startModifySSI()
				self.bos.delItemSSI(savethisuser).addCallback(cb)
				self.bos.endModifySSI()

                        elif(subtype == "unsubscribed"):
                                # The user wants to remove this contact's authorisation. Contact will no longer be able to see user
				debug.log("AIMConnection: Unsubscribed request received.")
				pass

                else: # The user wants to change subscription to the transport
                        if(subtype == "subscribe"):
                                updatePresence("subscribed")

                        elif(subtype == "subscribed"):
                                return # Nothing to do

                        elif(subtype == "unsubscribe" or subtype == "unsubscribed"):
                                # They want to unregister. Ok, we can do that
                                jid = self.session.jabberID
				debug.log("Subscriptions: Session \"%s\" is about to be unregistered" % (jid))
				self.session.pytrans.registermanager.removeRegInfo(jid)
				debug.log("Subscriptions: Session \"%s\" has been unregistered" % (jid))


	# Callbacks
	def errorCallback(self, result):
		debug.log("AIMConnection: errorCallback %s" % (result.getErrorMessage()))
		errmsg = result.getErrorMessage()
		errmsgs = errmsg.split("'")
		message = "Authentication Error!" 
		if (errmsgs[1]):
			message = message+"\n"+errmsgs[1]
		if (errmsgs[3]):
			message = message+"\n"+errmsgs[3]
		tmpjid = config.jid
		if (self.session.registeredmunge):
			tmpjid = tmpjid + "/registered"
		self.session.sendMessage(to=self.session.jabberID, fro=tmpjid, body=message, mtype="chat")

		self.session.removeMe()



#############################################################################
# AIMContacts
#############################################################################
class AIMContacts:
	def __init__(self, session):
		self.session = session
		self.ssicontacts = { }
		self.xdbcontacts = self.getXDBBuddies()
		self.xdbchanged = False

	def updateSSIContact(self, contact, presence="unavailable", show=None, status=None, skipsave=False):
		debug.log("AIMContacts: updating contact %s" % (contact.lower()))
		self.ssicontacts[contact.lower()] = {
			'presence': presence,
			'show': show,
			'status': status
		}

		if (not self.xdbcontacts.count(contact.lower())):
			from glue import aim2jid
			self.session.sendRosterImport(aim2jid(contact), "subscribe", "both", contact)
			self.xdbcontacts.append(contact.lower())
			self.xdbchanged = True
			if (not skipsave):
				self.saveXDBBuddies()

	def getXDBBuddies(self):
		debug.log("AIMContacts: getXDBBuddies %s %s" % (config.jid, self.session.jabberID))
		bl = list()
		result = self.session.pytrans.xdb.request(self.session.jabberID, "aimtrans:roster")
		if (result == None):
			debug.log("AIMContacts: getXDBBuddies unable to get list, or empty")
			return bl

		for child in result.childNodes:
			try:
				if(child.tagName == "buddies"):
					for item in child.childNodes:
						try:
							if(item.tagName == "item"):
								bl.append(item.attributes["name"])
						except AttributeError:
							continue
			except AttributeError:
				continue
		return bl

	def saveXDBBuddies(self):
		debug.log("AIMContacts: setXDBBuddies %s %s" % (config.jid, self.session.jabberID))
		if (not self.xdbchanged):
			debug.log("AIMContacts: nothing has changed, no need to save")
			return
		newXDB = Element("aimtrans")
		newXDB.namespace = "aimtrans:roster"
		buddies = Element("buddies")
		newXDB.appendChild(buddies)

		for c in self.xdbcontacts:
			try:
				item = Element("item")
				item.setAttribute("name", c)
				buddies.appendChild(item)

			except:
				pass

		self.session.pytrans.xdb.set(self.session.jabberID, "aimtrans:roster", newXDB)
		self.xdbchanged = False
		debug.log("AIMContacts: contacts saved")
